package rsj.service.export;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.fastjson.JSON;
import com.rlyy.aibase.db.Dbink;
import com.rlyy.aibase.utils.base.MapNode;
import com.rlyy.aibase.utils.base.Node;
import com.rlyy.aibase.utils.base.TableNode;
import com.rlyy.aibase.utils.base.ThinkException;
import common.exec.RsjIllegalArgueException;
import common.util.QueryRunnerFactory;
import common.util.http.UploadFileResponseData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import rsj.api.org.entity.ExportParam;
import rsj.api.org.entity.UsageReportParam;
import rsj.db.PostgresqlConn;
import rsj.domain.ExportLog;
import rsj.service.usage.ICalculateUsage;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.util.concurrent.CompletableFuture;

/**
 * 机构管理、产品管理使用情况导出、下载模板方法
 *
 * @author yuanqinglong
 * @since 2021/3/5 13:52
 */
public interface IExportTemplateMethod extends ICalculateUsage, IWriteUploadFileMethod {

    Logger logger = LoggerFactory.getLogger(IExportTemplateMethod.class);

    /**
     * 每次查询导出语句限制大小
     */
    int LIMIT_SIZES = 5000;

    /**
     * EXCEL每个工作表最大写入行数
     */
    int SHEET_SIZE = 100000;


    Object SYNC_WRITER = new Object();



    /**
     * 执行导出、下载方法
     *
     * @param usageReportParam     请求参数
     * @param exportTemplateMethod 实现类的Cglib事务代理对象
     * @return 返回结果
     */
    default MapNode performExport(UsageReportParam usageReportParam, IExportTemplateMethod exportTemplateMethod) {
        // 调用Service的事务代理对象
        // 1.保证数据可重复读 不会因为其他事务对数据库进行增删操作导致导出数据和统计数据对不上
        // 2.同一个线程多次查询使用同一个数据库连接、减少数据库连接的重复创建和销毁的消耗
        CompletableFuture.runAsync(() -> exportTemplateMethod.asyncExport(usageReportParam));
        return new MapNode().putNode("export_id", "111");
    }


    /**
     * 异步导出
     *
     * @param usageReportParam 使用情况报告参数
     */
    @Transactional(readOnly = true)
    default void asyncExport(UsageReportParam usageReportParam) {
        Connection connection = PostgresqlConn.getConnection();
        OutputStream outputStream = new ByteArrayOutputStream();
        ExcelWriter excelWriter = EasyExcelFactory.write(outputStream).build();
        ExportLog exportLog = new ExportLog();
        try {
            Node totalStatistics = this.totalStatistics(usageReportParam);
            exportLog.setExportCount(totalStatistics.valueInt());
            // 循环查询次数 = 记录总数 / 每次查询行数
            int cycles = totalStatistics.valueInt() > LIMIT_SIZES ? totalStatistics.valueInt() / LIMIT_SIZES : 1;
            // 判断总数是否能整除每次查询的行数  不能整除将查询次数加 1
            cycles = (totalStatistics.valueInt() % LIMIT_SIZES) == 0 ? cycles : cycles + 1;
            usageReportParam.setPageSize(LIMIT_SIZES);
            for (int i = 1; i <= cycles; i++) {
                usageReportParam.setPageNow((i - 1) * LIMIT_SIZES);
                // 计算工作表sheet号 序号从1开始  每个sheet 十万行
                int sheetNum = (((i - 1) * LIMIT_SIZES) / SHEET_SIZE) + 1;
                TableNode tableNode = this.queryTableByPage(usageReportParam);
                // 根据业务情况 二次处理数据预留接口 默认是空方法
                String jsonData = this.processDataAccordingToTheSituation(tableNode, connection, usageReportParam);
                this.writeDataToTheExcelWriter(jsonData, excelWriter, sheetNum);
            }
            // 同步写入excel 上传excel
            this.syncWriterFinish(outputStream, usageReportParam, excelWriter, exportLog);
            System.out.println(Thread.currentThread().getName() + ":执行完成。");
        } catch (ThinkException e) {
            logger.error("导出数据查询异常！参数：{}", JSON.toJSONString(usageReportParam), e);
            throw new RsjIllegalArgueException("导出数据查询异常！");
        } finally {
            Dbink.close(connection);
        }
    }


    /**
     * 同步写入
     *
     * @param excelWriter      优秀的作家
     * @param outputStream     输出流
     * @param usageReportParam 使用情况报告参数
     */
    default void syncWriterFinish(OutputStream outputStream, UsageReportParam usageReportParam, ExcelWriter excelWriter, ExportLog exportLog) {
        synchronized (SYNC_WRITER) {
            excelWriter.finish();
            this.uploadFileService(outputStream, usageReportParam, exportLog);
        }
    }


    /**
     * 上传文件服务
     *
     * @param outputStream     输出流
     * @param usageReportParam 使用情况报告参数
     * @param exportLog        导出日志
     * @return {@link MapNode}
     */
    default void uploadFileService(OutputStream outputStream, UsageReportParam usageReportParam, ExportLog exportLog) {
        // 将文件上传到文件服务器 返回文件id
        UploadFileResponseData fileResponseData;
        try {
            fileResponseData = this.uploadToFileService(outputStream, this.generateExportFileName(usageReportParam));
            logger.info("导出文件：{}", fileResponseData.getFileId());
            exportLog.setFileId(fileResponseData.getFileId());
            exportLog.setFileName(fileResponseData.getFileName());
            //return new MapNode().putNode("File_Id", fileResponseData.getFileId()).putNode("File_Name", fileResponseData.getFileName());
        } catch (Exception e) {
            logger.error("文件生成失败", e);
            throw new RsjIllegalArgueException("文件生成失败！");
        }
    }


    /**
     * 导出当前表
     *
     * @param usageReportParam 参数
     * @return {@link TableNode}
     */
    TableNode queryTableByPage(UsageReportParam usageReportParam);


    /**
     * 生成查询语句
     *
     * @param usageReportParam 参数
     * @return sql
     */
    default String generateQueryStatement(UsageReportParam usageReportParam) {
        return "";
    }


    /**
     * 统计总数
     *
     * @param usageReportParam 使用情况报告参数
     * @return int
     */
    default Node totalStatistics(UsageReportParam usageReportParam) {
        return new Node();
    }


    /**
     * 生成导出的文件名称
     *
     * @param usageReportParam usageReportParam参数
     * @return 文件名
     */
    String generateExportFileName(UsageReportParam usageReportParam);


    /**
     * 提供扩展的默认方法-根据不同业务情况重写此方法二次处理列表数据
     *
     * @param tableNode        列表数据
     * @param connection       数据库连接
     * @param usageReportParam usageReportParam参数
     * @return 处理后的列表数据
     */
    default String processDataAccordingToTheSituation(TableNode tableNode, Connection connection, UsageReportParam usageReportParam) {
        return tableNode.toJson();
    }


    /**
     * 通过机构id查询机构简称
     *
     * @param orgId 机构id
     * @return 机构简称
     */
    default String queryOrgNameById(String orgId) {
        String orgName;
        Connection connection = PostgresqlConn.getConnection();
        try {
            String sql = "SELECT org_sname FROM sys.sys_org WHERE org_id = '" + orgId + "'";
            orgName = QueryRunnerFactory.getQueryRunner().queryLinkNode(connection, sql).value();
        } finally {
            Dbink.close(connection);
        }
        return orgName;
    }


    /**
     * 通过接口id查询产品名称
     *
     * @param statProdUid 接口id
     * @return 机构简称
     */
    default String queryProdNameByStatProdUid(String statProdUid) {
        String orgName;
        Connection connection = PostgresqlConn.getConnection();
        try {
            String sql = "SELECT stat_prod_name FROM sys.sys_stat WHERE stat_prod_uid ='" + statProdUid + "'";
            orgName = QueryRunnerFactory.getQueryRunner().queryLinkNode(connection, sql).value();
        } finally {
            Dbink.close(connection);
        }
        return orgName;
    }


    /**
     * 转换参数实体
     *
     * @param usageReportParam 使用情况报告参数
     * @param statEnv          stat env
     * @return {@link ExportParam}
     */
    default ExportParam conversionParameterEntity(UsageReportParam usageReportParam, String statEnv) {
        return new ExportParam(usageReportParam.getStatOrgId(), usageReportParam.getStatProdUid(),
                usageReportParam.getStatOrgpId(), usageReportParam.getStatDate(), usageReportParam.getPageInfo().getPageSize(), usageReportParam.getPageInfo().getPageNow(), statEnv);
    }
}
